#include <queue>
#include <mutex>
#include <memory>
#include <condition_variable>
#include <iostream>
#include <string>


template<typename T>
class SafeQueue
{
public:
    SafeQueue() {}
    ~SafeQueue() {}

    void push(T new_data) {
        std::lock_guard<std::mutex> lk(m_mut);
        m_dataQueue.push(std::move(new_data));
        m_cond.notify_one();
    }

    void wait_and_pop(T& val) {
        std::unique_lock<std::mutex> ulk(m_mut);
        m_cond.wait(ulk,[this]() { return !m_dataQueue.empty(); });
        val=std::move(m_dataQueue.front());
        m_dataQueue.pop();
    }

    std::shared_ptr<T> wait_and_pop() {
        std::unique_lock<std::mutex> ulk(m_mut);
        m_cond.wait(ulk,[this]() { return !m_dataQueue.empty(); });
        std::shared_ptr<T> val(std::make_shared<T>(std::move(m_dataQueue.front())));
        m_dataQueue.pop();
        return val;
    }

    bool try_pop(T& val) {
        std::lock_guard<std::mutex> lk(m_mut);
        if(m_dataQueue.empty())
            return false;
        val=std::move(m_dataQueue.front());
        m_dataQueue.pop();
        return true;
    }

    std::shared_ptr<T> try_pop() {
        std::shared_ptr<T> val;
        std::lock_guard<std::mutex> lk(m_mut);
        if(m_dataQueue.empty())
            return val;
        val=std::make_shared<T>(std::move(m_dataQueue.front()));
        m_dataQueue.pop();
        return val;
    }

    bool empty() {
        std::lock_guard<std::mutex> lk(m_mut);
        return m_dataQueue.empty();
    }

    int count() {
        std::lock_guard<std::mutex> lk(m_mut);
        return m_dataQueue.size();
    }

private:
    std::queue<T> m_dataQueue;
    std::mutex m_mut;
    std::condition_variable m_cond;
};

int main(){
   SafeQueue<int> b;
   int a = 20;
   b.push(a);
   int c;
   b.wait_and_pop(c);
   std::cout<<c<<std::endl;

   SafeQueue<std::string> m;
   std::string n("adf");
   m.push(n);
   std::string i;
   m.wait_and_pop(i);
   std::cout<<i<<std::endl;
   std::cout<<m.count()<<std::endl;
}
